/*
	Copyright (C) 2009 Salil Bhagurkar
	
	This file is part of ViSE
	
	ViSE is free software: you can redistribute it and/or modify
	it under the terms of the GNU Lesser General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.
	
	ViSE is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU Lesser General Public License for more details.
	
	You should have received a copy of the GNU Lesser General Public License
	along with ViSE.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <shared.h>

int sched_init(struct node_fs *this);
int sched_deinit(struct node_fs *this);
int sched_task_notify(struct node_fs *this, struct node_fs *link);
int sched_synchronize_begin(struct node_fs *this, int code);
int sched_synchronize_end(struct node_fs *this, int code);

#define GET_TASK(fs) ((struct task *)(fs)->class)

//The main task registry
struct node_fs sched = {
	.type = TYPE_FS,
	.name = "sched",
	.init = sched_init,
	.deinit = sched_deinit,
	NODE_FS_OPS_INIT,
	NODE_FS_LIST_INIT,
};

//The task that is running currently. The task that refers to this always accesses its own task structure.
struct task *current;

struct node_fs sched_current_ptr = {
	.type = TYPE_FS,
	.name = "current_ptr",
	.class = &current,
	NODE_FS_INIT_INIT,
	NODE_FS_OPS_INIT,
	NODE_FS_LIST_INIT,
};
struct node_fs tasks = {
	.type = TYPE_FS,
	.name = "tasks",
	NODE_FS_INIT_INIT,
	.read = dummy,
	.write = dummy,
	.enumerate = dummy,
	.notify = sched_task_notify,
	.synchronize_begin = sched_synchronize_begin,
	.synchronize_end = sched_synchronize_end,
	NODE_FS_LIST_INIT,
};





//The preempting timer
struct node_fs idle_fs, test0_fs;
int sched_timer_callback(struct node_fs *this)
{
	struct task *to, *from;
	this->general_counter = 100;
	from = current;
	if(current->fs->next) {
		to = (struct task *)current->fs->next->class;
	} else {
		if(tasks.links->next) {
			to = (struct task *)(tasks.links->next->class); //Reinitialize to idle->next
		} else {
			to = (struct task *)(tasks.links->class); //We have no tasks to run, rerun idle
		}
	}
	if(to->first_run) {
		arch_prepare_first_switch(to->stack_ptr, &(from->stack_ptr));
		to->first_run = 0;
		current = to;
	} else if(to!=from) {
		arch_prepare_switch(to->stack_ptr, &(from->stack_ptr));
		current = to;
	}
	
	return 0;
}

//Task timeslice timer for sched
struct node_fs sched_timer = {
	.type = TYPE_FS,
	.name = "sched_timer",
	.init = sched_timer_callback,
	.deinit = dummy,
	NODE_FS_OPS_INIT,
	NODE_FS_LIST_INIT,
	.class = NULL,
	.general_counter = 100,
};





int sched_synchronize_begin(struct node_fs *this, int code)
{
	switch(code) {
		case SYNC_LIST_RW:
			sched_timer.general_counter = 0xffffffff;
			break;
	}
	return 0;
}


int sched_synchronize_end(struct node_fs *this, int code)
{
	switch(code) {
		case SYNC_LIST_RW:
			sched_timer.general_counter = 100;
			break;
	}
	return 0;
}


extern int main();
struct task main_task;
struct node_fs main_task_fs = {
	.type = TYPE_FS, .name = "main",
	NODE_FS_OPS_INIT,
	.init = main, .deinit = dummy,
	.class = &main_task,
	NODE_FS_LIST_INIT,
};


struct node_fs idle_fs;
int init_idle(struct node_fs *this)
{
	current = GET_TASK(&idle_fs);
	fs_link("/timed", &sched_timer);
	fs_link("/sched/tasks", &main_task_fs);
	while(1) {
		arch_halt();
	}
}

struct task idle;
struct node_fs idle_fs = {
	.type = TYPE_FS, .name = "idle",
	NODE_FS_OPS_INIT,
	.init = init_idle, .deinit = dummy,
	.class = &idle,
	NODE_FS_LIST_INIT,
};





/*This function allocates a 4kB stack for a process. The pointer is set at the end of the stack with an extra STACK_SAFE space beyond it*/
static int stack_malloc(void **stack_ptr)
{
	*(stack_ptr) = malloc(0x4100);
	if(!(*stack_ptr))
		return EMEMERR;
	(*(stack_ptr))+=0x4000;
	return 0;
}
/*This is called when a task registers in the /sched directory*/
int sched_task_notify(struct node_fs *this, struct node_fs *link)
{
	struct task *new_task;
	new_task = (struct task *)link->class; //Get the pointer to the task from the registered node
	stack_malloc(&new_task->stack);
	new_task->stack_ptr = new_task->stack;
	new_task->stack_ptr = arch_prepare_task(new_task->stack_ptr, link, link->init); //Prepare a proper stack pointer for the task
	new_task->fs = link;
	new_task->first_run = 1;
	printk"%s initialized, stack = %x\n", link->name, GET_TASK(link)->stack);
	return 0;
}

int sched_init(struct node_fs *this)
{
	//Start the task notifier in order to initiate tasks that register with us
	fs_link("/sched", &tasks);
	//Start up the idle task and let it do the remaining intiation.
	fs_link("/sched/tasks", &idle_fs);
	//Now the idle task has been initiated by sched_task_notify and now, we should simply dispatch it.
	GET_TASK(&idle_fs)->first_run = 0; //This task is manually started and no context is saved
	arch_start_task(GET_TASK(&idle_fs)->stack_ptr);
	while(1);
}

int sched_deinit(struct node_fs *this)
{
	return 0;
}


int register_sched()
{
	fs_link("/", &sched);
	return 0;
}
